package com.puzzlebazar.client.util;

import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Composite;
import com.puzzlebazar.client.main.view.ScreenSizeListener;

public class HorizontalSlider extends Composite implements Observer, ScreenSizeListener {

	protected static final int BAR_CONTAINER_HEIGHT = 40;
	protected static final int BAR_HEIGHT = 8;	
	protected static final int BAR_MARGIN_X = 12;

	private static final int MARKER_HEIGHT = 22;
	private static final int MARKER_WIDTH = 2;
	
	protected final AbsolutePanelWithControl barContainer = new AbsolutePanelWithControl();
	private final AbsolutePanel bar = new AbsolutePanel();
	private final AbsolutePanel marker = new AbsolutePanel();

	private FloatConnector observable;
	private float minValue, maxValue, value;

	protected int barWidth;

	private HandlerRegistration mouseMoveHandler;
	
	/**
	 * Creates a new horizontal slider
	 * 
	 * @param observable The float connector from which to extract the value
	 * @param minValue The minimum value of this slider
	 * @param maxValue The maximum value of this slider
	 */
	public HorizontalSlider( final FloatConnector observable, float minValue, float maxValue ) {
		barWidth = 1;

		this.minValue = minValue;
		this.maxValue = maxValue;
		
		value = observable.getFloatValue();
		
		barContainer.setWidth( "100%" );
		barContainer.setHeight( BAR_CONTAINER_HEIGHT + "px" );
		
		mouseMoveHandler = null;

		bar.setStylePrimaryName("grayfill");

		marker.setStylePrimaryName("blackfill");
		marker.setPixelSize( MARKER_WIDTH, MARKER_HEIGHT );

		barContainer.addMouseDownHandler( new MouseDownHandler(){
			@Override
			public void onMouseDown(MouseDownEvent event) {
				if( event.getNativeButton() == NativeEvent.BUTTON_LEFT ) {
					setValue( event.getRelativeX(bar.getElement()) );
					if( mouseMoveHandler == null ) {
						DOM.setCapture( barContainer.getElement() );
						mouseMoveHandler = barContainer.addMouseMoveHandler(new MouseMoveHandler(){
							@Override
							public void onMouseMove(MouseMoveEvent event) {							
								setValue( event.getRelativeX(bar.getElement()) );
							}
						});
					}
					event.stopPropagation();
				}
			}} );

		barContainer.addMouseUpHandler( new MouseUpHandler() {
			@Override
			public void onMouseUp(MouseUpEvent event) {
				if( event.getNativeButton() != NativeEvent.BUTTON_LEFT )
					return;
				if( mouseMoveHandler != null ) {
					DOM.releaseCapture( barContainer.getElement() );
					mouseMoveHandler.removeHandler();
					mouseMoveHandler = null;		
				}
				event.stopPropagation();
			}
		});
		
		
		barContainer.add(bar,BAR_MARGIN_X,(BAR_CONTAINER_HEIGHT-BAR_HEIGHT)/2);
		barContainer.add(marker);

		initWidget( barContainer );
		
		this.observable = observable;
		observable.addObserver( this );
	}

	@Override
	public void screenSizeChanged(Vec2i screenSize) {

		barWidth = screenSize.x - 2 * BAR_MARGIN_X;
		if( barWidth < 1 )
			barWidth = 1;
		
		bar.setPixelSize( barWidth, BAR_HEIGHT );
		
		changeMarkerLocation();
	}
	
	/**
	 * Updates the marker location
	 */
	protected void changeMarkerLocation() {
		int posY;
		float ratio = (value-minValue) / (maxValue-minValue);
		if( ratio < 0 )
			ratio = 0;
		else if( ratio > 1 )
			ratio = 1;
		int posX = BAR_MARGIN_X - MARKER_WIDTH/2 + (int)(ratio * barWidth);

		posY = (BAR_CONTAINER_HEIGHT - MARKER_HEIGHT)/2;
		barContainer.setWidgetPosition( marker, posX, posY );
	}
	
	@Override
	public void update(Observable observable, Object arg) {
		assert( observable == this.observable );
		float newValue = this.observable.getFloatValue();
		if( newValue == value )
			return;
		value = newValue;
		changeMarkerLocation();
	}

	@Override
	public void release() {
		observable.deleteObserver( this );
		observable = null;
	}
	
	/**
	 * Changes the value given the new position of the marker
	 * 
	 * @param markerPosX The x position within the bar to which to set the marker
	 */
	private void setValue(int markerPosX) {
		if( markerPosX < 0 || markerPosX >= barWidth )
			return;

		float ratio = (float)markerPosX / barWidth;
		float newValue = ratio * (maxValue - minValue) + minValue;
		if( newValue == value ) 
			return;
		value = newValue;
		changeMarkerLocation();
		try {
			observable.setFloatValue( value );
		} catch (Exception e) {
			e.printStackTrace();
		}
		observable.notifyObservers();
	}	
	
}
